﻿using System;
using System.IO;
using System.Windows.Forms;

namespace FreeX
{

    /// <summary>
    /// Extends the standard clipboard for better handling of files and directories
    /// </summary>
    public static class FreeXClipboard
    {

        private enum CurrentClipOp
        {
            Cut = 0,
            Copy = 1,
            None = 3
        }


        /// <summary> 
        /// Copys directory, including sub directories, if desired 
        /// </summary> 
        /// <param name="theSourcePath"></param> 
        /// <param name="theDestPath"></param> 
        /// <param name="copySubDirs"></param> 
        private static void CopyDirectory(String theSourcePath, string theDestPath, bool copySubDirs)
        {
            var dir = new DirectoryInfo(theSourcePath);
            DirectoryInfo[] dirs = dir.GetDirectories();

            // If the source directory does not exist, throw an exception. 
            if (!dir.Exists)
            {
                throw new DirectoryNotFoundException(
                    "Source directory does not exist or could not be found: "
                    + theSourcePath);
            }

            // If the destination directory does not exist, create it. 
            if (!Directory.Exists(theDestPath))
            {
                Directory.CreateDirectory(theDestPath);
            }


            // Get the file contents of the directory to copy. 
            FileInfo[] files = dir.GetFiles();

            foreach (FileInfo file in files)
            {
                // Create the path to the new copy of the file. 
                string temppath = Path.Combine(theDestPath, file.Name);

                // Copy the file. 
                file.CopyTo(temppath, false);
            }

            // If copySubDirs is true, copy the subdirectories. 
            if (copySubDirs)
            {

                foreach (DirectoryInfo subdir in dirs)
                {
                    // Create the subdirectory. 
                    string temppath = Path.Combine(theDestPath, subdir.Name);

                    // Copy the subdirectories. 
                    CopyDirectory(subdir.FullName, temppath, true);
                }
            }
        }

        private static void CopyFile(String theSourcePath, string theDestPath, bool doOverWrite)
        {
            var fi = new FileInfo(theSourcePath);

            if (!fi.Exists)
            {
                throw new FileNotFoundException();
            }
            File.Copy(theSourcePath, theDestPath, doOverWrite);
        }

        private static void MoveItem(String theSourcePath, String theDestPath)
        {
            var fa = File.GetAttributes(theSourcePath);
            if ((fa & FileAttributes.Directory) == FileAttributes.Directory)
            {
                Directory.Move(theSourcePath, theDestPath);
            }
            else
            {
                File.Move(theSourcePath, theDestPath);
            }
        }

        private static void CopyItem(String theSourcePath, String theDestPath)
        {
            var fa = File.GetAttributes(theSourcePath);
            if ((fa & FileAttributes.Device) == FileAttributes.Directory)
            {
                CopyDirectory(theSourcePath, theDestPath, true);
            }
            else
            {
                string destFilename = Path.GetFileName(theSourcePath);
                string destPath = Path.Combine(theDestPath, destFilename);
                CopyFile(theSourcePath, destPath, true);
            }

        }

        private static CurrentClipOp GetCutOrCopy()
        {
            const string fmt = "Preferred DropEffect";

            Object obj = Clipboard.GetData(fmt);

            if (obj != null)
            {
                // read in that clipboard item //
                var ms = (MemoryStream)obj;

                var br = new BinaryReader(ms);

                // the dropeffect shows up right away //
                var dde = (DragDropEffects)br.ReadInt32();

                // check to see what the effect is //
                switch (dde)
                {
                    case (DragDropEffects.Copy | DragDropEffects.Link):
                        return CurrentClipOp.Copy;
                    case DragDropEffects.Move:
                        return CurrentClipOp.Cut;
                    default:
                        return CurrentClipOp.None;
                }
            }

            return CurrentClipOp.None;
        }

        private static void SetClipboardData(string[] listOfSelectedFiles, DragDropEffects operation)
        {
            //System.Collections.Specialized.StringCollection currDroplist = new System.Collections.Specialized.StringCollection();

            string[] currDroplist = new string[listOfSelectedFiles.Length];

            for (var x = 0; x < listOfSelectedFiles.Length; x++)
            {
                //currDroplist.Add(ListOFSelectedFiles[x].ToString());
                currDroplist[x] = listOfSelectedFiles[x];
            }

            // we have to manually set the dragdropeffect bit when using our program //
            IDataObject data = new DataObject(DataFormats.FileDrop, currDroplist);
            var ms = new MemoryStream(4);

            // set the effect to move //
            if (operation == DragDropEffects.Move)
            {
                Byte[] ddf = new byte[] { (byte)DragDropEffects.Move, 0, 0, 0 };
                ms.Write(ddf, 0, ddf.Length);
            }
            else if (operation == DragDropEffects.Copy)
            {
                // set the effect to copy -- Copy is actually copy Or link//
                Byte[] ddf = new byte[] { (byte)(DragDropEffects.Copy | DragDropEffects.Link), 0, 0, 0 };
                ms.Write(ddf, 0, ddf.Length);
            }

            data.SetData("Preferred DropEffect", ms);

            // add the whole thing to the clipboard //
            Clipboard.SetDataObject(data);
        }

        /// <summary>
        /// Provides for placing a list of files in the FileDropList for a move operation
        /// </summary>
        /// <param name="listOfSelectedFiles">An array containing paths of files to place on clipboard</param>
        public static void Cut(string[] listOfSelectedFiles)
        {
            SetClipboardData(listOfSelectedFiles, DragDropEffects.Move);
        }

        /// <summary>
        /// Provides for placing a list of files in the FileDropList for a copy operation
        /// </summary>
        /// <param name="listOfSelectedFiles">An array containing paths of files to place on clipboard</param>
        public static void Copy(string[] listOfSelectedFiles)
        {
            SetClipboardData(listOfSelectedFiles, DragDropEffects.Copy);
        }

        /// <summary>
        /// Retrieves files from the clipboards FileDropList, then performs post-move operations on a cut
        /// </summary>
        /// <param name="destinationLocation"></param>
        public static void Paste(string destinationLocation)
        {
            CurrentClipOp op = GetCutOrCopy();

            System.Collections.Specialized.StringCollection fileDropStrings;

            if (Clipboard.ContainsFileDropList())
            {
                fileDropStrings = Clipboard.GetFileDropList();
            }

            else
            {
                return;
            }

            switch (op)
            {
                case CurrentClipOp.Copy:
                    for (Int32 x = 0; x <= fileDropStrings.Count - 1; x++)
                    {
                        CopyItem(fileDropStrings[x], destinationLocation);
                    }
                    break;
                case CurrentClipOp.Cut:
                    for (Int32 x = 0; x <= fileDropStrings.Count - 1; x++)
                    {
                        MoveItem(fileDropStrings[x], destinationLocation);
                    }
                    break;
            }
        }

        /// <summary>
        /// Implements drag drop copy and move
        /// </summary>
        /// <param name="destinationLocation">original files path</param>
        /// <param name="sourceFile">the path where item is to be dropped</param>
        /// <param name="op">copy or move</param>
        public static void Drop(string destinationLocation, string sourceFile, DragDropEffects op)
        {
            string completeDestination;
            bool isDirectory;

            var fi = new FileInfo(sourceFile);

            if (fi.Attributes == FileAttributes.Directory)
            {
                completeDestination = destinationLocation;
                isDirectory = true;
            }
            else
            {
                completeDestination = Path.Combine(destinationLocation, Path.GetFileName(sourceFile));
                isDirectory = false;
            }



            if (op == DragDropEffects.Copy)
            {
                if (isDirectory)
                {
                    CopyDirectory(sourceFile, completeDestination, true);
                }
                else
                {
                    CopyFile(sourceFile, completeDestination, true);
                }
            }

            else if (op == DragDropEffects.Move)
            {
                MoveItem(sourceFile, completeDestination);
            }
        }


    }
}
